package com.github.zyf.ioaj.algorithm;

import com.github.zyf.ioaj.constant.ParamType;
import com.github.zyf.ioaj.annotation.internal.Param;
import com.github.zyf.ioaj.annotation.internal.ParamItem;
import com.github.zyf.ioaj.exception.InputException;

import java.util.Arrays;

/**
 * <h1>算法输入量</h1>
 * <p>
 * 使用构造函数时，需要输入四个变量
 * <ul>
 *     <li>目标函数：{@link #function}</li>
 *     <li>变量个数：{@link #varNum}</li>
 *     <li>自变量取值下限：{@link #lb}，Double[] 类型</li>
 *     <li>自变量取值上限：{@link #ub}，Double[] 类型</li>
 * </ul>
 * 注：自变量上下限不是基础类型是因为目标函数的需要，目标函数需要实现{@link Function}接口，对应的泛型不能是基础类型
 * <p>默认求解最小值，如果需要求解最大值，将目标函数乘以-1即可，最后得到的值再次乘以-1就是得到的最优值（自变量取值不用乘以-1）</p>
 * <p>
 *     默认求解的目标函数是连续型函数，如果是离散型的，最后将自变量取值取整即可，
 *     按向上和向下两种取整方式来分，共有C_{varNum}^{2}种方式，最优值就是其中一种。
 *     算法内部暂未实现，开发者如有需要可以自己实现。
 * </p>
 * <p>
 * 如果对于已求解完成的算例，如需获取输入量相关信息，方法如下
 * <ul>
 *     <li>获取目标函数：{@link #getFunction()}</li>
 *     <li>获取变量个数：{@link #getVarNum()}</li>
 *     <li>获取自变量取值下限：{@link #getLb()}</li>
 *     <li>获取自变量取值上限：{@link #getUb()}</li>
 * </ul>
 * <p>
 * 此外，该类实现了{@link #toString()}方法，如需转字符串（该方法一般用于程序调试输出，无换行），可以使用该方法，如有特殊需求：
 * <ul>
 *     <li>中文文本（有换行）：{@link #toTxt()}</li>
 *     <li>Json字符串（英文，无换行）：{@link #toJson()}</li>
 * </ul>
 *
 * @author zyf
 */
@Param
public class Input {

    /**
     * <p>目标函数</p>
     * <p>类型：{@link Function}</p>
     * <p>函数输入类型：Double[]，长度为{@link #varNum}</p>
     * <p>函数输出类型：Double</p>
     * <p>注意，在定义函数时，f(x,y,z)分别对应了x[0],x[1],x[2]，即用数组存储自变量</p>
     */
    @ParamItem(desc = "目标函数")
    private final Function function;

    /**
     * <p>变量个数</p>
     * <p>类型：Integer</p>
     * <p>例如：y=x+1，变量个数是1；y=x+y+1，变量个数是2</p>
     * <p>对应的目标函数分别是：y=x[0]+1, y=x[0]+x[1]+1</p>
     */
    @ParamItem(type = ParamType.INT, desc = "变量个数")
    private final int varNum;

    /**
     * <p>自变量取值下限，Double[varNum]，其长度一定要是{@link #varNum}</p>
     * <p>否则在构造函数阶段会抛出 {@link InputException}</p>
     */
    @ParamItem(type = ParamType.DOUBLE_ARRAY_1, desc = "自变量取值下限")
    private final double[] lb;

    /**
     * <p>自变量取值上限，Double[varNum]，其长度一定要是{@link #varNum}</p>
     * <p>否则在构造函数阶段会抛出 {@link InputException}</p>
     */
    @ParamItem(type = ParamType.DOUBLE_ARRAY_1, desc = "自变量取值上限")
    private final double[] ub;

    /**
     * <p>创建输入量</p>
     * <p>输入量不能为NULL，否则会抛出异常</p>
     * <p>输入的自变量取值上下限需要与变量个数一致，否则会抛出异常</p>
     *
     * @param function 目标函数 {@link #function}
     * @param varNum   变量个数 {@link #varNum}
     * @param lb       变量取值下限 {@link #lb}
     * @param ub       变量取值上限 {@link #ub}
     */
    public Input(Function function, int varNum, double[] lb, double[] ub) {
        if (function == null || lb == null || ub == null)
            throw new InputException("输入的参数不能为NULL");
        this.function = function;
        this.varNum = varNum;
        this.lb = lb;
        this.ub = ub;
        if (lb.length != varNum || ub.length != varNum)
            throw new InputException("变量取值上下限与变量个数不一致");
    }


    public Function getFunction() {
        return function;
    }

    public int getVarNum() {
        return varNum;
    }

    public double[] getLb() {
        return lb;
    }

    public double[] getUb() {
        return ub;
    }

    /**
     * 获取上下限差值 ub[j] - lb[j]
     *
     * @param j 索引
     * @return double
     */
    public double getRange(int j) {
        return ub[j] - lb[j];
    }

    public double[] gerRange() {
        double[] range = new double[varNum];
        int j;
        for (j = 0; j < varNum; j++) {
            range[j] = ub[j] - lb[j];
        }
        return range;
    }

    /**
     * 随机生成 lb[j] ~ ub[j] 之间的一个值
     *
     * @param j 索引
     * @return double
     */
    public double randomValueInRange(int j) {
        return lb[j] + (Math.random() * 32767.0 / (32767.0 + 1.0)) * (ub[j] - lb[j]);
    }

    /**
     * 计算函数值，判断A是否优于B（即A的函数值是否小于B的函数值）
     *
     * @param A 变量 A
     * @param B 变量 B
     * @return f(A) 小于 f(B) 返回 true
     */
    public boolean is_A_BetterThan_B(double[] A, double[] B) {
        return function.apply(A) < function.apply(B);
    }

    /**
     * 计算函数值，判断A的函数值是否优于函数值F
     * @param A 变量 A
     * @param F 函数值 F
     * @return f(A) 小于 F 返回 true
     */
    public boolean is_A_BetterThan_F(double[] A, double F) {
        return function.apply(A) < F;
    }

    public double[] chooseBestParam(double[]... params) {
        int len = params.length;
        int i;
        double[] best = params[0];
        for (i = 0; i < len; i++) {
            if (is_A_BetterThan_B(params[i], best)) {
                best = params[i];
            }
        }
        return best;
    }

    @Override
    public String toString() {
        return "Input{" +
                "function=" + function +
                ", varNum=" + varNum +
                ", lb=" + Arrays.toString(lb) +
                ", ub=" + Arrays.toString(ub) +
                '}';
    }

    public String toTxt() {
        return "\n---\n算法输入内容如下：" +
                "\n目标函数对象为：" + function +
                "\n变量个数为：" + varNum +
                "\n自变量取值下限为：" + Arrays.toString(lb) +
                "\n自变量取值上限为：" + Arrays.toString(ub) + "\n---";
    }

    public String toJson() {
        return "\"input\": {" +
                "\"function\": \"" + function +
                "\", \"varNum\": " + varNum +
                ", \"lb\": " + Arrays.toString(lb) +
                ", \"ub\": " + Arrays.toString(ub) +
                "}";
    }
}
